package ckw.gui;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.ByteArrayTransfer;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import ckw.database.model.Election;
import ckw.database.model.ElectionNode;
import ckw.database.model.Member;

public class WzdPgAddEditElectionRKWMembers extends ModelValidationWizardPage {

	private static class RKWTreeContentProvider implements ITreeContentProvider {
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}

		public void dispose() {
		}

		public Object[] getElements(Object inputElement) {
			return getChildren(inputElement);
		}

		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof Election) {
				Election election = (Election) parentElement;
				return election.getNodesAsSortedList().toArray();
			}
			if (parentElement instanceof ElectionNode) {
				ElectionNode electionNode = (ElectionNode) parentElement;
				return electionNode.getMembers().toArray();
			}
			// Member
			return new Object[] {};
		}

		public Object getParent(Object element) {
			if (element instanceof Election) {
				return null;
			}
			if (element instanceof ElectionNode) {
				ElectionNode electionNode = (ElectionNode) element;
				return electionNode.getElection();
			}
			return null;
		}

		public boolean hasChildren(Object element) {
			return getChildren(element).length > 0;
		}
	}

	private static class RKWTreeLabelProvider extends LabelProvider {
		public Image getImage(Object element) {
			return super.getImage(element);
		}

		public String getText(Object element) {
			if (element instanceof Election) {
				return element == null ? "" : ((Election) element).getName();
			}
			if (element instanceof ElectionNode) {
				return element == null ? "" : ((ElectionNode) element).getNode().getName();
			}
			if (element instanceof Member) {
				Member member = (Member) element;
				return element == null ? "" : member.getSurname() + ", " + member.getName();
			}
			return super.getText(element);
		}
	}

	private static class MemberTreeContentProvider implements ITreeContentProvider {
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}

		public void dispose() {
		}

		public Object[] getElements(Object inputElement) {
			return getChildren(inputElement);
		}

		@SuppressWarnings("unchecked")
		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof List) {
				List<Member> memberList = (List<Member>) parentElement;
				return memberList.toArray();
			}
			return new Object[] {};
		}

		public Object getParent(Object element) {
			return null;
		}

		public boolean hasChildren(Object element) {
			return getChildren(element).length > 0;
		}
	}

	private static class MemberTreeLabelProvider extends LabelProvider {
		public Image getImage(Object element) {
			return super.getImage(element);
		}

		public String getText(Object element) {
			Member member = (Member) element;
			return element == null ? "" : member.getSurname() + ", " + member.getName();
		}
	}

	private static class MemberTransfer extends ByteArrayTransfer {

		private static final String MEMBER_TRANSFER_TYPE_NAME = "MemberTransferType";
		private static final int MEMBER_TRANSFER_TYPE_ID = registerType(MEMBER_TRANSFER_TYPE_NAME);

		private static final MemberTransfer _instance = new MemberTransfer();

		public static MemberTransfer getInstance() {
			return _instance;
		}

		@Override
		protected boolean validate(Object object) {
			return hasValidType(object);
		}

		/**
		 * Sprawdza, czy obiekt jest typu Integer[].
		 * 
		 * @param object
		 *            obiekt sprawdzany
		 * @return true - dla obiektu typu Integer[]; false - dla pozostałych
		 */
		private boolean hasValidType(Object object) {
			if (object == null || !(object instanceof Integer[]) || ((Integer[]) object).length == 0) {
				return false;
			}
			Integer[] memberIds = (Integer[]) object;
			for (int i = 0; i < memberIds.length; i++) {
				if (memberIds[i] == null) {
					return false;
				}
			}
			return true;
		}

		@Override
		protected void javaToNative(Object object, TransferData transferData) {
			if (!hasValidType(object) || !isSupportedType(transferData)) {
				DND.error(DND.ERROR_INVALID_DATA);
			}
			Integer[] memberIds = (Integer[]) object;
			try {
				// write data to a byte array and then ask super to convert to
				// pMedium
				ByteArrayOutputStream out = new ByteArrayOutputStream();
				DataOutputStream writeOut = new DataOutputStream(out);
				for (Integer memberId : memberIds) {
					writeOut.writeInt(memberId);
				}
				byte[] buffer = out.toByteArray();
				writeOut.close();
				super.javaToNative(buffer, transferData);
			} catch (IOException e) {
			}
		}

		@Override
		protected Object nativeToJava(TransferData transferData) {
			if (isSupportedType(transferData)) {
				byte[] buffer = (byte[]) super.nativeToJava(transferData);
				if (buffer == null) {
					return null;
				}

				List<Integer> memberIds = new ArrayList<Integer>();
				try {
					ByteArrayInputStream in = new ByteArrayInputStream(buffer);
					DataInputStream readIn = new DataInputStream(in);
					while (readIn.available() > 0) {
						Integer memberId = readIn.readInt();
						memberIds.add(memberId);
					}
				} catch (IOException ex) {
					return null;
				}
				return memberIds.toArray(new Integer[memberIds.size()]);
			}

			return null;
		}

		@Override
		protected int[] getTypeIds() {
			return new int[] { MEMBER_TRANSFER_TYPE_ID };
		}

		@Override
		protected String[] getTypeNames() {
			return new String[] { MEMBER_TRANSFER_TYPE_NAME };
		}
	}

	private boolean addElection;
	private Election election;

	private Map<Integer, Member> idToMemberMap;
	private List<Member> allMembersList;

	private TreeViewer trVwrRKWs;
	private Tree trRKWs;
	private TreeViewer trVwrMembers;
	private Tree trMembers;

	private Widget dragSource;

	/**
	 * Create the wizard.
	 */
	public WzdPgAddEditElectionRKWMembers(boolean addElection, Election election) {
		super("WzdPgAddEditElectionRKWMembers");

		this.addElection = addElection;
		this.election = election;

		if (addElection) {
			setTitle("Add Elections Members to RKWs");
		} else {
			setTitle("Edit Elections Members to RKWs");
		}

		setDescription("Set Elections Members to RKWs");
	}

	/**
	 * Create contents of the wizard.
	 * 
	 * @param parent
	 */
	public void createControl(Composite parent) {
		Composite cmpMain = new Composite(parent, SWT.NULL);

		setControl(cmpMain);
		cmpMain.setLayout(new GridLayout(2, false));

		Label lblRkws = new Label(cmpMain, SWT.NONE);
		lblRkws.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
		lblRkws.setText("RKWs");

		Label lblMembers = new Label(cmpMain, SWT.NONE);
		lblMembers.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
		lblMembers.setText("Members");

		trVwrRKWs = new TreeViewer(cmpMain, SWT.BORDER | SWT.MULTI);
		trVwrRKWs.setContentProvider(new RKWTreeContentProvider());
		trVwrRKWs.setLabelProvider(new RKWTreeLabelProvider());
		trRKWs = trVwrRKWs.getTree();
		trRKWs.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				List<TreeItem> selectionList = new ArrayList<TreeItem>();
				for (TreeItem treeItem : trRKWs.getSelection()) {
					if (treeItem.getData() instanceof Member) {
						selectionList.add(treeItem);
					}
				}
				trRKWs.setSelection(selectionList.toArray(new TreeItem[selectionList.size()]));
			}
		});
		trRKWs.setLinesVisible(true);
		trRKWs.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

		Transfer[] types = new Transfer[] { MemberTransfer.getInstance() };
		final List<Integer> selectedMemberIds = new ArrayList<Integer>();

		DragSource trRKWDragSource = new DragSource(trRKWs, DND.DROP_MOVE);
		trRKWDragSource.setTransfer(types);
		trRKWDragSource.addDragListener(new DragSourceListener() {
			@Override
			public void dragStart(DragSourceEvent event) {
				dragSource = trRKWs;
				for (TreeItem treeItem : trRKWs.getSelection()) {
					if (treeItem.getData() instanceof Member) {
						Member member = (Member) treeItem.getData();
						selectedMemberIds.add(member.getId().intValue());
					}
				}
				if (selectedMemberIds.isEmpty()) {
					event.doit = false;
				} else {
					event.doit = true;
				}
			}

			@Override
			public void dragSetData(DragSourceEvent event) {
				event.data = selectedMemberIds.toArray(new Integer[selectedMemberIds.size()]);
			}

			@Override
			public void dragFinished(DragSourceEvent event) {
				selectedMemberIds.clear();
				dragSource = null;
			}
		});
		DropTarget trRKWDropTarget = new DropTarget(trRKWs, DND.DROP_MOVE);
		trRKWDropTarget.setTransfer(types);
		trRKWDropTarget.addDropListener(new DropTargetAdapter() {
			@Override
			public void drop(DropTargetEvent event) {
				if (event.data == null || event.item == null) {
					event.detail = DND.DROP_NONE;
					return;
				}

				TreeItem dropTargetTreeItem = (TreeItem) event.item;
				ElectionNode dropTargetElectionNode;
				if (dropTargetTreeItem.getData() instanceof ElectionNode) {
					dropTargetElectionNode = (ElectionNode) dropTargetTreeItem.getData();
				} else {
					// Member
					dropTargetElectionNode = (ElectionNode) dropTargetTreeItem.getParentItem().getData();
				}
				if (dragSource == trRKWs) {
					for (TreeItem treeItem : trRKWs.getSelection()) {
						ElectionNode selectionElectionNode;
						if (treeItem.getData() instanceof Member) {
							selectionElectionNode = (ElectionNode) treeItem.getParentItem().getData();
							if (dropTargetElectionNode != selectionElectionNode) {
								Member member = (Member) treeItem.getData();
								manager.removeMember(selectionElectionNode, member);
								manager.addMember(dropTargetElectionNode, member);
							}
						}
					}
				} else {
					for (Integer memberId : (Integer[]) event.data) {
						manager.addMember(dropTargetElectionNode, idToMemberMap.get(memberId));
					}
				}
				refreshPage();
				validate();
			}

			@Override
			public void dragOver(DropTargetEvent event) {
				event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
				if (event.item != null) {
					TreeItem item = (TreeItem) event.item;
					if (item.getData() instanceof ElectionNode) {
						event.feedback |= DND.FEEDBACK_INSERT_AFTER;
					}
					if (item.getData() instanceof Member) {
						Point pt = getShell().getDisplay().map(null, trRKWs, event.x, event.y);
						Rectangle bounds = item.getBounds();
						if (pt.y < bounds.y + bounds.height / 3) {
							event.feedback |= DND.FEEDBACK_INSERT_BEFORE;
						} else if (pt.y > bounds.y + 2 * bounds.height / 3) {
							event.feedback |= DND.FEEDBACK_INSERT_AFTER;
						} else {
							event.feedback |= DND.FEEDBACK_SELECT;
						}
					}
				}
			}
		});

		trVwrMembers = new TreeViewer(cmpMain, SWT.BORDER | SWT.MULTI);
		trVwrMembers.setContentProvider(new MemberTreeContentProvider());
		trVwrMembers.setLabelProvider(new MemberTreeLabelProvider());
		trMembers = trVwrMembers.getTree();
		trMembers.setLinesVisible(true);
		trMembers.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

		DragSource trMembersDragSource = new DragSource(trMembers, DND.DROP_MOVE);
		trMembersDragSource.setTransfer(types);
		trMembersDragSource.addDragListener(new DragSourceListener() {
			@Override
			public void dragStart(DragSourceEvent event) {
				dragSource = trMembers;
				for (TreeItem treeItem : trMembers.getSelection()) {
					if (treeItem.getData() instanceof Member) {
						Member member = (Member) treeItem.getData();
						selectedMemberIds.add(member.getId().intValue());
					}
				}
				if (selectedMemberIds.isEmpty()) {
					event.doit = false;
				} else {
					event.doit = true;
				}
			}

			@Override
			public void dragSetData(DragSourceEvent event) {
				event.data = selectedMemberIds.toArray(new Integer[selectedMemberIds.size()]);
			}

			@Override
			public void dragFinished(DragSourceEvent event) {
				selectedMemberIds.clear();
				dragSource = null;
			}
		});
		DropTarget trMembersDropTarget = new DropTarget(trMembers, DND.DROP_MOVE);
		trMembersDropTarget.setTransfer(types);
		trMembersDropTarget.addDropListener(new DropTargetAdapter() {
			@Override
			public void drop(DropTargetEvent event) {
				if (event.data == null) {
					event.detail = DND.DROP_NONE;
					return;
				}
				if (dragSource == trRKWs) {
					for (TreeItem treeItem : trRKWs.getSelection()) {
						if (treeItem.getData() instanceof Member) {
							Member member = (Member) treeItem.getData();
							ElectionNode electionNode = (ElectionNode) treeItem.getParentItem().getData();
							manager.removeMember(electionNode, member);
						}
					}
					refreshPage();
					validate();
				}
			}

			@Override
			public void dragOver(DropTargetEvent event) {
				event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
				if (event.item != null) {
					TreeItem item = (TreeItem) event.item;
					if (item.getData() instanceof Member) {
						Point pt = getShell().getDisplay().map(null, trMembers, event.x, event.y);
						Rectangle bounds = item.getBounds();
						if (pt.y < bounds.y + bounds.height / 3) {
							event.feedback |= DND.FEEDBACK_INSERT_BEFORE;
						} else if (pt.y > bounds.y + 2 * bounds.height / 3) {
							event.feedback |= DND.FEEDBACK_INSERT_AFTER;
						} else {
							event.feedback |= DND.FEEDBACK_SELECT;
						}
					}
				}
			}
		});

		initValues();
		setValidationListeners();
	}

	private void initValues() {
		idToMemberMap = new HashMap<Integer, Member>();

		if (addElection) {
			setPageComplete(false);
		} else {
			for (ElectionNode electionNode : election.getNodes()) {
				for (Member member : electionNode.getMembers()) {
					idToMemberMap.put(member.getId().intValue(), member);
				}
			}
		}

		allMembersList = manager.getAllMembers();
		for (Member member : allMembersList) {
			int memberId = member.getId().intValue();
			if (idToMemberMap.get(memberId) == null) {
				idToMemberMap.put(memberId, member);
			}
		}
	}

	private void setValidationListeners() {
		// TODO Auto-generated method stub

	}

	private List<Member> getFreeMembersForElection() {
		Set<Integer> selectedMemberIds = new HashSet<Integer>();
		for (ElectionNode electionNode : election.getNodes()) {
			for (Member member : electionNode.getMembers()) {
				selectedMemberIds.add(member.getId().intValue());
			}
		}
		List<Member> freeMembersList = new ArrayList<Member>();
		for (Member member : allMembersList) {
			if (selectedMemberIds.contains(member.getId().intValue()) == false) {
				freeMembersList.add(member);
			}
		}
		return freeMembersList;
	}

	@Override
	protected void onEnterPage() {
		trVwrRKWs.setInput(election);
		trVwrRKWs.expandAll();
		trVwrMembers.setInput(getFreeMembersForElection());
		validate();
	}

	protected void refreshPage() {
		trVwrRKWs.setInput(election);
		trVwrRKWs.expandAll();
		trVwrMembers.setInput(getFreeMembersForElection());
	}

	@Override
	protected void validate() {
		if (MainWindow.isInDebugMode()) {
			clearErrorState();
			return;
		}

		// error
		if (allMembersList.size() < election.getNodes().size()) {
			setErrorState("There is too little Members to meet the at-least-one-Member-per-RKW requirement.");
			return;
		}
		for (ElectionNode electionNode : election.getNodes()) {
			if (electionNode.getMembers().isEmpty()) {
				setErrorState("Each RKW must have at least 1 Member.");
				return;
			}
		}

		// ok
		clearErrorState();
	}

	@Override
	protected void update() {

	}
}
